using Serilog;
using System.CommandLine;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;

namespace Launcher
{
    public sealed class DynamicAssemblyLoadContext : AssemblyLoadContext, IDisposable
    {
        private readonly string _fullAssemblyDirectory;
        private readonly string _entryAssemblyPath;
        private Thread? _executionThread;
        private string[]? _cachedCommandLineArgs;
        private bool _disposed;
        private readonly CancellationTokenSource _cts = new();
        private readonly Dictionary<string, string> _assemblyPathCache = new(StringComparer.OrdinalIgnoreCase);

        public AssemblyLoaderParameters Parameters { get; }

        public DynamicAssemblyLoadContext(AssemblyLoaderParameters parameters)
            : base(parameters.AssemblyLoadContextName, isCollectible: true)
        {
            Parameters = parameters ?? throw new ArgumentNullException(nameof(parameters));

            _fullAssemblyDirectory = Path.GetFullPath(parameters.AssemblyDirectory);
            _entryAssemblyPath = Path.Combine(_fullAssemblyDirectory, parameters.EntryAssemblyFile);

            if (!Directory.Exists(_fullAssemblyDirectory))
                throw new DirectoryNotFoundException($"程序集目录不存在: {_fullAssemblyDirectory}");

            if (!File.Exists(_entryAssemblyPath))
                throw new FileNotFoundException($"入口程序集文件不存在: {_entryAssemblyPath}");

            BuildAssemblyPathCache();
            this.Resolving += OnResolvingAssembly;
        }

        private void BuildAssemblyPathCache()
        {
            var asmFiles = DllLoaderHelper.GetAllDllFiles(_fullAssemblyDirectory);

            Log.Logger.Information("在目录中找到 {Count} 个 DLL 文件用于加载", asmFiles.Length);

            foreach (var file in asmFiles)
            {
                try
                {
                    var assemblyName = AssemblyName.GetAssemblyName(file);
                    if (!string.IsNullOrEmpty(assemblyName.FullName))
                    {
                        _assemblyPathCache[assemblyName.FullName] = file;
                    }
                }
                catch (BadImageFormatException) { /* 跳过非托管DLL */ }
            }
        }

        public Task ExecuteAsync(CancellationToken cancellationToken = default, TimeSpan? timeout = null)
        {
            if (_disposed)
                throw new ObjectDisposedException(nameof(DynamicAssemblyLoadContext));

            var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cts.Token);
            if (timeout.HasValue)
            {
                linkedCts.CancelAfter(timeout.Value);
            }

            var tcs = new TaskCompletionSource<bool>();

            _executionThread = new Thread(() => ExecuteOnThread(tcs, linkedCts.Token))
            {
                IsBackground = true,
                Name = $"ALC-Thread-{Parameters.AssemblyLoadContextName}[{Guid.NewGuid().ToString()}]"
            };

            ConfigureThreadApartmentState(_executionThread);
            _executionThread.Start();

            return tcs.Task;
        }

        private void ExecuteOnThread(TaskCompletionSource<bool> tcs, CancellationToken cancellationToken)
        {
            try
            {
                cancellationToken.ThrowIfCancellationRequested();
                Log.Logger.Information("应用程序启动: {EntryAssembly}", Parameters.EntryAssemblyFile);

                ExecuteEntryPoint();

                Log.Logger.Information("程序执行完成");
                tcs.SetResult(true);
            }
            catch (OperationCanceledException)
            {
                Log.Logger.Information("程序执行已取消");
                tcs.SetCanceled();
            }
            catch (Exception ex)
            {
                Log.Logger.Error(ex, "执行过程中发生错误");
                tcs.SetException(ex);
            }
        }

        private void ConfigureThreadApartmentState(Thread thread)
        {
            if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return;

            if (Enum.TryParse<ApartmentState>(Parameters.ThreadApartmentState, true, out var state))
            {
                thread.SetApartmentState(state);
            }
            else
            {
                throw new ArgumentException($"无效的线程单元状态: {Parameters.ThreadApartmentState}");
            }
        }

        private void CancelExecution()
        {
            if (_executionThread?.IsAlive == true)
            {
                try
                {
                    _cts.Cancel();
                }
                catch (Exception ex)
                {
                    Log.Logger.Warning(ex, "中断线程时发生错误");
                    throw; // 抛出异常让上层处理
                }
            }
        }

        private string[] GetCommandLineArgs()
        {
            return _cachedCommandLineArgs ??= ParseCommandLineArgs();
        }

        private string[] ParseCommandLineArgs()
        {
            try
            {
                var rootCommand = new RootCommand();
                var result = rootCommand.Parse(Parameters.CommandLineArgs);
                return result.Tokens.Select(t => t.Value).ToArray();
            }
            catch (Exception ex)
            {
                Log.Logger.Error(ex, "解析命令行参数失败");
                throw; // 抛出异常让上层处理
            }
        }

        private void ExecuteEntryPoint()
        {
            try
            {
                Log.Logger.Information("开始加载主程序集");
                var mainAssembly = LoadEntryAssembly();
                Log.Logger.Information("成功加载主程序集: {FullName}", mainAssembly.FullName);

                LoadAllDependencies(mainAssembly);

                var mainMethod = GetEntryPointMethod(mainAssembly);
                if (mainMethod == null)
                {
                    throw new InvalidOperationException("找不到有效的入口方法");
                }

                Log.Logger.Information("开始执行入口方法");
                InvokeEntryPoint(mainMethod);
            }
            catch (Exception ex)
            {
                Log.Logger.Error(ex, "执行入口点时发生错误");
                throw; // 重新抛出异常
            }
        }

        private void LoadAllDependencies(Assembly rootAssembly)
        {
            var loadedAssemblies = new HashSet<string>(
                this.Assemblies.Select(a => a.GetName().FullName),
                StringComparer.OrdinalIgnoreCase
            );

            var assemblies = rootAssembly.GetReferencedAssemblies();

            Log.Logger.Information("入口程序集引用了 {Count} 个依赖", assemblies.Length);

            foreach (var asmRef in assemblies)
            {
                try
                {
                    if (loadedAssemblies.Contains(asmRef.FullName))
                    {
                        Log.Logger.Information("跳过已加载依赖: {FullName}", asmRef.FullName);
                        continue;
                    }

                    Log.Logger.Information("正在加载依赖: {FullName}", asmRef.FullName);
                    var loadedAssembly = this.LoadFromAssemblyName(asmRef);
                    loadedAssemblies.Add(loadedAssembly.GetName().FullName);
                }
                catch (Exception ex)
                {
                    Log.Logger.Error(ex, $"加载依赖 {asmRef.Name} 失败");
                    throw; // 重新抛出异常
                }
            }
        }

        private Assembly LoadEntryAssembly()
        {
            try
            {
                var trueAssemblyName = AssemblyName.GetAssemblyName(_entryAssemblyPath);
                var loadedAssembly = Assemblies.FirstOrDefault(a =>
                    AssemblyName.ReferenceMatchesDefinition(a.GetName(), trueAssemblyName));

                if (loadedAssembly != null)
                {
                    Log.Logger.Debug("入口程序集已加载: {AssemblyName}", trueAssemblyName.FullName);
                    return loadedAssembly;
                }

                Log.Logger.Information("加载入口程序集: {FilePath}", _entryAssemblyPath);
                return LoadFromAssemblyPath(_entryAssemblyPath);
            }
            catch (Exception ex)
            {
                Log.Logger.Error(ex, "加载入口程序集失败");
                throw; // 重新抛出异常
            }
        }

        private MethodInfo GetEntryPointMethod(Assembly assembly)
        {
            try
            {
                var type = assembly.GetType(Parameters.EntryType)
                    ?? throw new InvalidOperationException($"类型 {Parameters.EntryType} 不存在");

                var mainMethod = type.GetMethod("Main",
                        BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
                    ?? throw new InvalidOperationException($"{Parameters.EntryType}.Main 方法不存在");

                Log.Logger.Information("找到 Main 方法: {TypeName}.{MethodName}()",
                    mainMethod.DeclaringType?.FullName, mainMethod.Name);

                return mainMethod;
            }
            catch (Exception ex)
            {
                Log.Logger.Error(ex, "获取入口方法失败");
                throw; // 重新抛出异常
            }
        }

        private void InvokeEntryPoint(MethodInfo mainMethod)
        {
            try
            {
                var parameters = mainMethod.GetParameters();
                object?[] args = parameters.Length switch
                {
                    0 => Array.Empty<object>(),
                    1 when parameters[0].ParameterType == typeof(string[]) => new object[] { GetCommandLineArgs() },
                    _ => throw new InvalidOperationException(
                        $"Main方法签名不支持: 期望 void Main() 或 void Main(string[] args), 实际为 {mainMethod}")
                };

                mainMethod.Invoke(null, args);
            }
            catch (Exception ex)
            {
                Log.Logger.Error(ex, "执行入口方法失败");
                throw; // 重新抛出异常
            }
        }

        private Assembly? OnResolvingAssembly(AssemblyLoadContext context, AssemblyName assemblyName)
        {
            try
            {
                if (string.IsNullOrEmpty(assemblyName.Name))
                {
                    throw new ArgumentException("无效的程序集名称");
                }

                var loadedAssembly = Assemblies.FirstOrDefault(a =>
                    AssemblyName.ReferenceMatchesDefinition(a.GetName(), assemblyName));

                if (loadedAssembly != null)
                {
                    return loadedAssembly;
                }

                if (_assemblyPathCache.TryGetValue(assemblyName.FullName, out var path))
                {
                    return LoadFromAssemblyPath(path);
                }

                var fuzzyMatch = _assemblyPathCache.Keys.FirstOrDefault(k =>
                    k.StartsWith($"{assemblyName.Name},"));

                if (fuzzyMatch != null)
                {
                    return LoadFromAssemblyPath(_assemblyPathCache[fuzzyMatch]);
                }

                return null;
            }
            catch (Exception ex)
            {
                Log.Logger.Error(ex, $"解析程序集 {assemblyName} 时发生错误");
                throw; // 重新抛出异常
            }
        }

        public void Dispose()
        {
            if (_disposed) return;

            try
            {
                this.Resolving -= OnResolvingAssembly;
                _cts.Cancel();

                if (_executionThread?.IsAlive == true)
                {
                    _executionThread.Join(1000);
                }
            }
            finally
            {
                _executionThread = null;
                _cts.Dispose();
                _disposed = true;
                GC.SuppressFinalize(this);
            }
        }

        ~DynamicAssemblyLoadContext()
        {
            if (!_disposed)
            {
                Log.Logger.Warning("DynamicAssemblyLoadContext 未被正确释放");
                Dispose();
            }
        }
    }
}